vlwkaos' digital garden

Structural vs Nominal Subtyping 그리고 TypeScript

타입은 문자열(string), 불린(boolean), 오브젝트 처럼 이름과 구조를 가진 것을 말한다.

  • 문자열, 불린 타입은 primitive 타입으로 구조가 매우 간단하다.
  • 오브젝트는 이름과 구조 및 형태가 각기 다르고 복잡하다.

정적 타입 체커는 일반적으로 이름이나 구조를 이용하여 타입의 다름을 구분한다.

  • 이름으로 타입을 구분하는 것을 Nominal Typing이라고 한다.
  • 구조로 타입을 구분하는 것을 structual Typing이라고 한다.

Nominal Subtyping

C++, Java와 같은 언어는 Nominal 타입 시스템을 이용한다.

이름이 다를 경우 다른 타입이므로 다음과 같은 코드는 불가능하다.

// pseudo code이다
class A {
    method(input: string): number {...}
}
class B {
    method(input: string): number {...}
}

let a: A = new B(); // 에러

Structural Subtyping

Ocaml, Haskell, Javascript는 Structural 타입 시스템을 이용한다.

이름이 다르더라도 구조가 같으므로 다음과 같은 코드도 괜찮다.

// pseudo code이다
class A {
    method(input: string): number {...}
}
class B {
    method(input: string): number {...}
}

let a: A = new B(); // 통과

대신 아래처럼 구조가 바뀌면 다른 타입으로 인식한다.

// pseudo code이다
class A {
    method(input: string): number {...}
}
class B {
    method(input: number): number {...}
}

let a: A = new B(); // 에러

TypeScript의 타입 시스템

TypeScript는 기본적으로 Structural 타입 시스템이다. TypeScript는 결국 JavaScript로 변환되기 때문에 JavaScript의 성질을 따를 수 밖에 없기 때문이다.

하지만 약간의 코드로 Nominal 타입의 이점을 가져갈 수 있다. 제약 조건 역할을 하는 특수한 속성(__brand, __는 제약을 위한 속성의 이름의 컨벤션이다.)과 Intersectional 타입을 이용하여 ValidatedInputString에 우리가 원하는 문자열만 저장할 수 있도록 제한할 수 있다.

type ValidatedInputString = string & { __brand: "User Input Post Validation" }

이제 제약조건을 만족시키는 문자열로 변환하는 함수를 만들어보자

const validateUserInput = (input: string) => {
  const simpleValidatedInput = input.replace(/\</g, "<=")
  return simpleValidatedInput as ValidatedInputString
}

제약조건을 만족하는 문자열만 인자로 받아 출력하는 함수를 만들어보자

const printName = (name: ValidatedInputString) => {
  console.log(name)
}

원하지 않는 문자열이 전달됐을 때 validateUserInput을 통해 변환되어 출력되는 것을 볼 수 있다.

const input = "\n<script>alert('bobby tables')</script>"
const validated = validateUserInput(input)
printName(validated)

문자열을 그냥 출력하려할 경우 타입 체커가 에러를 뱉게 된다.

printName(input) // 에러

TypeScript를 이용한 nominal 타입에 관심이 있다면 다음 두 글을 읽어보자

Structural vs Nominal Subtyping 그리고 TypeScript